#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "fat-edit.h"

int main(int argc, const char* argv[]) {
	if (argc == 1) {
		printf("Error: missing second argument.\n");
		return 0;
	}

	file = fopen(argv[1], "r+");

	if (file == NULL) {
		printf("Error: file could not be opened.\n");
		return 0;
	}

	readBoot();
	calculations();
	readFSInfo();
	//readRoot();

	strcpy(curDir, "/");
	curClust = bootSec.BPB_RootClus;

	char *input = (char *) malloc(MAX_INPUT_LENGTH);
	char *token = (char *) malloc(MAX_FILENAME_LENGTH);
	char *userName = getenv("USER");
	bool quit = false;
	int i;
	while (!quit) {
		printf("%s(%s)>", userName, argv[1]);
		fgets(input, MAX_INPUT_LENGTH, stdin);
		for (i = 0; i < strlen(input); i++) {
			if (input[i] == '\n')
				input[i] = 0;
		}
		if (strchr(input, '/')) {
			printf("Error: file names cannot contain /");
			continue;
		}
		strcpy(token, strtok(input, " "));
		if (strcmp(token, "quit") == 0) {
			quit = true;
		} else if (strcmp(token, "fsinfo") == 0) {
			fsInfo();
		} else if (strcmp(token, "open") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char *mode = (char *) malloc(3);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing arguments.\n");
				continue;
			}
			strcpy(filename, temp);
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing arguments.\n");
				continue;
			}
			strcpy(mode, temp);
			struct ret r = open(filename, mode);
			if (!r.status)
				printf("%s\n", r.message);
			free(filename);
			free(mode);
		} else if (strcmp(token, "close") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			struct ret r = close(filename);
			if (!r.status)
				printf("%s\n", r.message);
			free(filename);
		} else if (strcmp(token, "create") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			create(filename);
			free(filename);
		} else if (strcmp(token, "read") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char *integer = (char *) malloc(MAX_FILENAME_LENGTH);
			uint startPos, numBytes;
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing start position.\n");
				continue;
			}
			strcpy(integer, temp);
			sscanf(integer, "%u", &startPos);
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing number of bytes to read.\n");
				continue;
			}
			strcpy(integer, temp);
			sscanf(integer, "%u", &numBytes);
			struct ret r = read(filename, startPos, numBytes);
			if (!r.status)
				printf("%s\n", r.message);
			free(filename);
			free(integer);
		} else if (strcmp(token, "write") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char *integer = (char *) malloc(MAX_FILENAME_LENGTH);
			uint startPos;
			char *quotedData = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing start position.\n");
				continue;
			}
			strcpy(integer, temp);
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing data.\n");
				continue;
			}
			strcpy(quotedData, temp);

			quotedData += 1; // remove first and last quote
			quotedData[strlen(quotedData) - 1] = 0;

			sscanf(integer, "%u", &startPos);
			struct ret r = write(filename, startPos, quotedData);
			if (!r.status)
				printf("%s\n", r.message);
			free(filename);
			free(integer);
			free(quotedData - 1);
		} else if (strcmp(token, "rm") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			rm(filename);
			free(filename);
		} else if (strcmp(token, "srm") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			struct ret r = srm(filename);
			if (!r.status)
				printf("%s\n", r.message);
			free(filename);
		} else if (strcmp(token, "cd") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing directory name.\n");
				continue;
			}
			strcpy(filename, temp);
			cd(filename);
			free(filename);
		} else if (strcmp(token, "ls") == 0) {
			ls();
		} else if (strcmp(token, "mkdir") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing directory name.\n");
				continue;
			}
			strcpy(filename, temp);
			mkdir(filename);
			free(filename);
		} else if (strcmp(token, "rmdir") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing directory name.\n");
				continue;
			}
			strcpy(filename, temp);
			rmdir(filename);
			free(filename);
		} else if (strcmp(token, "size") == 0) {
			char *filename = (char *) malloc(MAX_FILENAME_LENGTH);
			char* temp;
			temp = strtok(NULL, " ");
			if (temp == NULL) {
				printf("Error: missing file name.\n");
				continue;
			}
			strcpy(filename, temp);
			int s = size(filename);
			if (s == 0)
				printf("Error: %s is not open.\n", filename);
			else
				printf("%d\n", s);
			free(filename);
		} else {
			printf("Error: that command was not recognized.\n");
		}
	}
	free(input);
	free(token);

	fclose(file);
	return 0;
}

void fsInfo() {
	printf("bytes per sector: %d\n", bootSec.BPB_BytesPerSec);
	printf("sectors per cluster: %d\n", bootSec.BPB_SecPerClus);
	printf("total sectors: %d\n", bootSec.BPB_TotSec32);
	printf("number of FATs: %d\n", bootSec.BPB_NumFATs);
	printf("sectors per FAT: %d\n", bootSec.BPB_FATSz32);
	printf("number of free sectors: %d\n", FSInfo.FSI_Free_Count);
}

struct ret open(char name[], char mode[]) {
	struct secOfEnts ents;
	bool found = false;
	uint32_t nextCluster = curClust;
	int x;

	// scan oftable first
	for (x = 0; x < MAX_OPEN_FILES; x++) {
		if (strncmp(ofTable[x].name, name, strlen(name)) == 0) {
			struct ret r;
			r.status = false;
			strcpy(r.message, "Error: ");
			strcat(r.message, name);
			strcat(r.message, " is already open.");

			return r;
		}
	}

	while (!found) {
		ents = readCluster(nextCluster);
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {

				if (ents.entries[x].DIR_Attr != 0x10) // not a directory
				{
					found = true;

					struct oftEntry newEntry;
					newEntry.cluster = ents.entries[x].DIR_FstClustHI << 8
							| ents.entries[x].DIR_FstClusLO;
					if (strcmp(mode, "r") == 0)
						newEntry.mode = 0;
					else if (strcmp(mode, "w") == 0)
						newEntry.mode = 1;
					else if (strcmp(mode, "rw") == 0 || strcmp(mode, "wr") == 0)
						newEntry.mode = 2;
					else {
						struct ret r;
						r.status = false;
						strcpy(r.message, "Error: \"");
						strcat(r.message, mode);
						strcat(r.message, "\" invalid mode.");

						return r;
					}
					int y;
					for (y = strlen(name); y > 0; y--) {
						if (name[y] == 32)
							name[y] = 0;
					}
					strcpy(newEntry.name, name);
					newEntry.size = ents.entries[x].DIR_FileSize;
					newEntry.dirEntCluster = nextCluster;

					for (y = 0; y < MAX_OPEN_FILES; y++) {
						if (ofTable[y].size == 0) {
							ofTable[y] = newEntry;

							break;
						}
					}

					printf("%s has been opened for ", name);
					switch (newEntry.mode) {
					case 0:
						printf("reading\n");
						break;
					case 1:
						printf("writing\n");
						break;
					case 2:
						printf("reading and writing\n");
						break;
					}

					struct ret r;
					r.status = true;
					strcpy(r.message, "");

					return r;
				} else {
					struct ret r;
					r.status = false;
					strcpy(r.message, "Error: ");
					strcat(r.message, name);
					strcat(r.message, " is a directory.");

					return r;
				}
			}
		}

		nextCluster = readFatEntry(nextCluster);

		if (nextCluster == EoC) {
			struct ret r;
			r.status = false;
			strcpy(r.message, "Error: ");
			strcat(r.message, name);
			strcat(r.message, " was not found.");

			return r;
		}
	}

	struct ret r;
	r.status = false;
	strcpy(r.message, "File not found.");

	return r;
}

struct ret close(char name[]) {
	int x;
	for (x = 0; x < MAX_OPEN_FILES; x++) {
		if (strncmp(ofTable[x].name, name, strlen(name)) == 0) {
			ofTable[x].name[0] = 0;
			ofTable[x].size = 0;

			printf("%s has been closed\n", name);
			struct ret r;
			r.status = true;
			strcpy(r.message, "");

			return r;
		}
	}

	struct ret r;
	r.status = false;
	strcpy(r.message, "Error: ");
	strcat(r.message, name);
	strcat(r.message, " is not open.");

	return r;
}

bool create(char name[]) {
	int x;
	struct secOfEnts ents;
	uint32_t curClus = curClust;
	while (curClus < EoC) {
		ents = readCluster(curClus);
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
				printf("That name is already taken.\n");
				return false;
			}
		}
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (ents.entries[x].DIR_Name[0] == (char) 0xE5
					|| ents.entries[x].DIR_Name[0] == (char) 0x00) {
				strcpy(ents.entries[x].DIR_Name, name);
				uint32_t nextFreeCluster = FSInfo.FSI_Nxt_Free;
				writeFatEntry(nextFreeCluster, EoC, 1);
				ents.entries[x].DIR_FstClusLO = (uint16_t) nextFreeCluster;
				nextFreeCluster = nextFreeCluster >> 16;
				ents.entries[x].DIR_FstClustHI = (uint16_t) nextFreeCluster;
				ents.entries[x].DIR_FileSize = 0;
				ents.entries[x].DIR_Attr = 0;
				updateSectorDirEnts(curClus, ents);

				nextFreeCluster = findFreeCluster();
				FSInfo.FSI_Nxt_Free = nextFreeCluster;
				FSInfo.FSI_Free_Count--;
				updateFSInfo();
				return true;
			}
		}
		curClus = readFatEntry(curClus);
	}

	if (!allocateNewCluster(curClus)) {
		printf("There is no more space.\n");
		return false;
	}
	curClus = readFatEntry(curClus);

	ents = readCluster(curClus);
	for (x = 0; x < ENT_PER_CLUS; x++) {
		if (x + 1 == ENT_PER_CLUS)
			ents.entries[0].DIR_Name[0] = (char) 0x00;
		else
			ents.entries[0].DIR_Name[0] = (char) 0xE5;
	}
	strcpy(ents.entries[x].DIR_Name, name);
	uint32_t nextFreeCluster = FSInfo.FSI_Nxt_Free;
	writeFatEntry(nextFreeCluster, EoC, 1);
	ents.entries[x].DIR_FstClusLO = (uint16_t) nextFreeCluster;
	nextFreeCluster = nextFreeCluster >> 16;
	ents.entries[x].DIR_FstClustHI = (uint16_t) nextFreeCluster;
	ents.entries[x].DIR_FileSize = 0;
	ents.entries[x].DIR_Attr = 0;
	updateSectorDirEnts(curClus, ents);

	nextFreeCluster = findFreeCluster();
	FSInfo.FSI_Nxt_Free = nextFreeCluster;
	FSInfo.FSI_Free_Count--;
	updateFSInfo();
	return true;
}

struct ret read(char name[], uint startPos, uint numBytes) {
	// find cluster in open file table
	struct oftEntry ent;
	bool found = false;

	if (startPos > ent.size || startPos > size(name)) {
		struct ret r;
		r.status = false;
		strcpy(r.message, "Error: start position starts after end of file.");

		return r;
	}

	int x;
	for (x = 0; x < MAX_OPEN_FILES; x++) {
		if (strncmp(ofTable[x].name, name, strlen(name)) == 0) {
			found = true;
			ent = ofTable[x];

			break;
		}
	}

	if (!found) {
		struct ret r;
		r.status = false;
		strcpy(r.message, "Error: ");
		strcat(r.message, name);
		strcat(r.message, " is not open.");

		return r;
	}

	if (ent.mode != 0 && ent.mode != 2) // not in read mode
	{
		struct ret r;
		r.status = false;
		strcpy(r.message, "Error: ");
		strcat(r.message, name);
		strcat(r.message, " is not in read mode.");

		return r;
	}

	char read[numBytes + 1];
	for (x = 0; x <= numBytes; x++)
		read[x] = 0;

	uint curClus = ent.cluster;
	uint offset = clusterOffsetInBytes(curClus);
	uint curPosInFile = 0;
	uint readIndex = 0;
	uint numBytesLeft = numBytes;
	uint bytesRead;

	while (numBytesLeft > 0 && curClus < EoC && curPosInFile < ent.size) {
		// get to first cluster for startPos
		while ((curPosInFile + bootSec.BPB_BytesPerSec) < startPos) {
			curPosInFile += bootSec.BPB_BytesPerSec;
			curClus = readFatEntry(curClus);
			offset = clusterOffsetInBytes(curClus);
		}

		if (curPosInFile < startPos) {
			curPosInFile += startPos % bootSec.BPB_BytesPerSec;
			offset += startPos % bootSec.BPB_BytesPerSec;
		}

		bytesRead = bootSec.BPB_BytesPerSec - (curPosInFile
				% bootSec.BPB_BytesPerSec);

		if (bytesRead + curPosInFile > ent.size)
			bytesRead = ent.size - curPosInFile;

		if (bytesRead > numBytesLeft)
			bytesRead = numBytesLeft;

		fseek(file, offset, SEEK_SET);
		fread((void*) &read[readIndex], bytesRead, 1, file);

		readIndex += bytesRead;
		curPosInFile += bytesRead;
		numBytesLeft -= bytesRead;

		curClus = readFatEntry(curClus);
		offset = clusterOffsetInBytes(curClus);
	}

	printf("%s\n", read);

	struct ret r;
	r.status = true;
	strcpy(r.message, "");

	return r;
}

struct ret write(char name[], uint startPos, char quotedData[]) {
	int x;
	for (x = 0; x < MAX_OPEN_FILES; x++) {
		if (strncmp(ofTable[x].name, name, strlen(name)) == 0) {
			if (ofTable[x].mode == 1 || ofTable[x].mode == 2) {
				int dataLen = strlen(quotedData);

				while (size(name) < startPos + dataLen) // allocate new space
				{
					if (!allocateNewCluster(ofTable[x].cluster)) {
						struct ret r;
						r.status = false;
						strcpy(r.message, "Error: no more free space.");

						return r;
					}
				}

				uint writeIndex = 0;
				uint curClus = ofTable[x].cluster;
				uint offset = clusterOffsetInBytes(curClus);
				uint curPosInFile = 0;
				int numBytesLeft = dataLen;
				uint bytesWrite;

				while (numBytesLeft > 0) {
					// get to first cluster for startPos
					while ((curPosInFile + bootSec.BPB_BytesPerSec) < startPos) {
						curPosInFile += bootSec.BPB_BytesPerSec;
						curClus = readFatEntry(curClus);
						offset = clusterOffsetInBytes(curClus);
					}

					if (curPosInFile < startPos) {
						curPosInFile += startPos % bootSec.BPB_BytesPerSec;
						offset += startPos % bootSec.BPB_BytesPerSec;
					}

					bytesWrite = bootSec.BPB_BytesPerSec - (curPosInFile
							% bootSec.BPB_BytesPerSec);

					if (bytesWrite > numBytesLeft)
						bytesWrite = numBytesLeft;

					fseek(file, offset, SEEK_SET);
					fwrite((void*) &quotedData[writeIndex], bytesWrite, 1, file);

					writeIndex += bytesWrite;
					curPosInFile += bytesWrite;
					numBytesLeft -= bytesWrite;

					curClus = readFatEntry(curClus);
					offset = clusterOffsetInBytes(curClus);
				}

				// update dirEntry and ofTable if filesize has changed
				struct secOfEnts ents = readCluster(ofTable[x].dirEntCluster);
				int y;

				for (y = 0; y < ENT_PER_CLUS; y++) {
					if (strncmp(ents.entries[y].DIR_Name, name, strlen(name))
							== 0) {
						int tempSize = startPos + strlen(quotedData);

						if (tempSize > ofTable[x].size) {
							// update ofT
							ofTable[x].size = tempSize;
							// update dirEnts
							ents.entries[y].DIR_FileSize = tempSize;
							updateSectorDirEnts(ofTable[x].dirEntCluster, ents);
						}

						break;
					}
				}

				printf("wrote \"%s\" at %d of length %d to %s\n", quotedData,
						startPos, strlen(quotedData), name);
				struct ret r;
				r.status = true;
				strcpy(r.message, "");

				return r;
			} else { // not in write mode
				struct ret r;
				r.status = false;
				strcpy(r.message, "Error: ");
				strcat(r.message, name);
				strcat(r.message, " is not in write mode.");

				return r;
			}
		}
	}

	// not found
	struct ret r;
	r.status = false;
	strcpy(r.message, "Error: ");
	strcat(r.message, name);
	strcat(r.message, " is not open.");

	return r;
}

bool rm(char name[]) {
	int x;
	uint32_t startClus, curClus, prevClus;
	struct secOfEnts ents;
	ents = readCluster(curClust);
	for (x = 0; x < ENT_PER_CLUS; x++) {
		if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
			if (ents.entries[x].DIR_Attr == 0x10) {
				printf("That file is a directory.\n");
				return false;
			} else {
				startClus = ents.entries[x].DIR_FstClustHI << 8
						| ents.entries[x].DIR_FstClusLO;
				curClus = readFatEntry(startClus);
				while (curClus != EoC) {
					while (curClus < EoC) {
						prevClus = curClus;
						curClus = readFatEntry(curClus);
					}
					uint32_t endOC = findLastFileFatEntry(prevClus);
					writeFatEntry(endOC, EoC, 1);
					writeFatEntry(prevClus, 0x00000000, 1);
					writeFatEntry(endOC, EoC, 2);
					writeFatEntry(prevClus, 0x00000000, 2);
					curClus = readFatEntry(startClus);
				}
				writeFatEntry(startClus, 0x00000000, 1);
				writeFatEntry(startClus, 0x00000000, 2);
				if (x + 1 == ENT_PER_CLUS)
					ents.entries[x].DIR_Name[0] = (char) 0x00;
				else
					ents.entries[x].DIR_Name[0] = (char) 0xE5;
				updateSectorDirEnts(curClust, ents);
				return true;
			}
		}
	}
	printf("%s is not in the current directory.\n", name);
	return false;
}

struct ret srm(char name[]) {
	int x;
	uint32_t startClus, curClus, prevClus;
	struct secOfEnts ents;
	ents = readCluster(curClust);
	for (x = 0; x < ENT_PER_CLUS; x++) {
		if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
			if (ents.entries[x].DIR_Attr == 0x10) {
				struct ret r;
				r.status = false;
				strcpy(r.message, "Error: ");
				strcat(r.message, name);
				strcat(r.message, " is a directory.");

				return r;
			} else {
				startClus = ents.entries[x].DIR_FstClustHI << 8
						| ents.entries[x].DIR_FstClusLO;
				curClus = readFatEntry(startClus);

				while (curClus < EoC) {
					while (curClus < EoC) {
						prevClus = curClus;
						curClus = readFatEntry(curClus);
					}

					uint32_t offset = clusterOffsetInBytes(curClus);
					char tmp = 0;
					fseek(file, offset, SEEK_SET);
					fwrite((void*) &tmp, sizeof(tmp), bootSec.BPB_BytesPerSec,
							file);

					uint32_t endOC = findLastFileFatEntry(prevClus);
					writeFatEntry(endOC, EoC, 1);
					writeFatEntry(prevClus, 0x00000000, 1);
					writeFatEntry(endOC, EoC, 2);
					writeFatEntry(prevClus, 0x00000000, 2);
					curClus = readFatEntry(startClus);
				}

				uint32_t offset = clusterOffsetInBytes(startClus);
				char tmp = 0;
				fseek(file, offset, SEEK_SET);
				fwrite((void*) &tmp, sizeof(tmp), bootSec.BPB_BytesPerSec, file);

				writeFatEntry(startClus, 0x00000000, 1);
				writeFatEntry(startClus, 0x00000000, 2);

				if (x + 1 == ENT_PER_CLUS)
					ents.entries[x].DIR_Name[0] = (char) 0x00;
				else
					ents.entries[x].DIR_Name[0] = (char) 0xE5;

				updateSectorDirEnts(curClust, ents);

				struct ret r;
				r.status = true;
				strcpy(r.message, "");

				return r;
			}
		}
	}

	struct ret r;
	r.status = false;
	strcpy(r.message, "Error: ");
	strcat(r.message, name);
	strcat(r.message, " does not exist.");

	return r;
}

bool cd(char name[]) {
	int x;
	struct secOfEnts ents;
	uint32_t curClus = curClust;
	while(curClus != EoC) {
		ents = readCluster(curClus);
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
				if (ents.entries[x].DIR_Attr == 0x10) {
					curClust = ents.entries[x].DIR_FstClustHI << 8
							| ents.entries[x].DIR_FstClusLO;
					if(curClust == 0) {
						curClust = bootSec.BPB_RootClus;
					}
					//strcpy(curDir, ents.entries[x].DIR_Name);
					return true;
				} else {
					printf("%s is not a directory.\n", name);
					return false;
				}

			}
		}
		curClus = readFatEntry(curClus);
		if(findLastFileFatEntry(curClus) == curClust)
			curClus = EoC;
	}
	printf("%s is not in the current directory.\n", name);
	return false;
}

bool mkdir(char name[]) {
	int x;
	struct secOfEnts ents;
	uint32_t curClus = curClust;
	while (curClus < EoC) {
		ents = readCluster(curClus);
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
				printf("That name is already taken.\n");
				return false;
			}
		}
		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (ents.entries[x].DIR_Name[0] == (char) 0xE5
					|| ents.entries[x].DIR_Name[0] == (char) 0x00) {
				strcpy(ents.entries[x].DIR_Name, name);
				uint32_t nextFreeCluster = FSInfo.FSI_Nxt_Free;
				writeFatEntry(nextFreeCluster, EoC, 1);
				addDotFiles(nextFreeCluster, curClus);
				ents.entries[x].DIR_FstClusLO = (uint16_t) nextFreeCluster;
				nextFreeCluster = nextFreeCluster >> 16;
				ents.entries[x].DIR_FstClustHI = (uint16_t) nextFreeCluster;
				ents.entries[x].DIR_FileSize = 0;
				ents.entries[x].DIR_Attr = 0x10;
				updateSectorDirEnts(curClus, ents);

				nextFreeCluster = findFreeCluster();
				FSInfo.FSI_Nxt_Free = nextFreeCluster;
				FSInfo.FSI_Free_Count--;
				updateFSInfo();
				return true;
			}
		}
		curClus = readFatEntry(curClus);
	}

	if (!allocateNewCluster(curClus)) {
		printf("There is no more space.\n");
		return false;
	}
	curClus = readFatEntry(curClus);

	ents = readCluster(curClus);
	for (x = 0; x < ENT_PER_CLUS; x++) {
		if (x + 1 == ENT_PER_CLUS)
			ents.entries[0].DIR_Name[0] = (char) 0x00;
		else
			ents.entries[0].DIR_Name[0] = (char) 0xE5;
	}
	strcpy(ents.entries[x].DIR_Name, name);
	uint32_t nextFreeCluster = FSInfo.FSI_Nxt_Free;
	writeFatEntry(nextFreeCluster, EoC, 1);
	addDotFiles(nextFreeCluster, curClus);
	ents.entries[x].DIR_FstClusLO = (uint16_t) nextFreeCluster;
	nextFreeCluster = nextFreeCluster >> 16;
	ents.entries[x].DIR_FstClustHI = (uint16_t) nextFreeCluster;
	ents.entries[x].DIR_FileSize = 0;
	ents.entries[x].DIR_Attr = 0x10;
	updateSectorDirEnts(curClus, ents);

	nextFreeCluster = findFreeCluster();
	FSInfo.FSI_Nxt_Free = nextFreeCluster;
	FSInfo.FSI_Free_Count--;
	updateFSInfo();
	return true;
}

bool rmdir(char name[]) {
	int x, i;
	uint32_t startClus, curClus, prevClus;
	struct secOfEnts ents, dirents;
	;
	ents = readCluster(curClust);
	for (x = 0; x < ENT_PER_CLUS; x++) {
		if (strncmp(name, ents.entries[x].DIR_Name, strlen(name)) == 0) {
			if (ents.entries[x].DIR_Attr != 0x10) {
				printf("That file is not directory.\n");
				return false;
			} else {
				startClus = ents.entries[x].DIR_FstClustHI << 8
						| ents.entries[x].DIR_FstClusLO;
				dirents = readCluster(startClus);
				for (i = 2; i < ENT_PER_CLUS; i++) {
					if (dirents.entries[i].DIR_Name[0] != (char) 0x00
							&& dirents.entries[i].DIR_Name[0] != (char) 0xE5) {
						printf("Requested directory is not empty!\n");
						return false;
					}
				}
				curClus = readFatEntry(startClus);
				while (curClus != EoC) {
					while (curClus < EoC) {
						prevClus = curClus;
						curClus = readFatEntry(curClus);
					}
					uint32_t endOC = findLastFileFatEntry(prevClus);
					writeFatEntry(endOC, EoC, 1);
					writeFatEntry(prevClus, 0x00000000, 1);
					writeFatEntry(endOC, EoC, 2);
					writeFatEntry(prevClus, 0x00000000, 2);
					curClus = readFatEntry(startClus);
				}
				writeFatEntry(startClus, 0x00000000, 1);
				writeFatEntry(startClus, 0x00000000, 2);
				if (x + 1 == ENT_PER_CLUS)
					ents.entries[x].DIR_Name[0] = (char) 0x00;
				else
					ents.entries[x].DIR_Name[0] = (char) 0xE5;
				updateSectorDirEnts(curClust, ents);
				return true;
			}
		}
	}
	printf("%s is not in the current directory.\n", name);
	return false;
}

uint size(char name[]) {
	struct oftEntry ent;
	bool found = false;

	int x;
	for (x = 0; x < MAX_OPEN_FILES; x++) {
		if (strncmp(ofTable[x].name, name, strlen(name)) == 0) {
			found = true;
			ent = ofTable[x];

			break;
		}
	}

	if (!found)
		return 0;

	uint curC = ent.cluster;
	int size = 0;

	do {
		size += bootSec.BPB_BytesPerSec;
	} while ((curC = readFatEntry(curC)) < EoC);

	return size;
}

void ls() {
	uint32_t curClus = curClust;

	while (curClus < EoC) {
		struct secOfEnts ents = readCluster(curClus);
		int x;

		for (x = 0; x < ENT_PER_CLUS; x++) {
			if (ents.entries[x].DIR_Name[0] != (char) 0x00
					&& ents.entries[x].DIR_Name[0] != (char) 0xE5) {
				char name[12];
				strcpy(name, ents.entries[x].DIR_Name);

				int y;
				for (y = strlen(name); y > 0; y--) {
					if (name[y] == 32)
						name[y] = 0;
				}
				printf("%s ", name);
			}
		}

		curClus = readFatEntry(curClus);
	}

	printf("\n");
}

void readBoot() {
	fseek(file, 0x0B, SEEK_SET);
	fread((void*) &bootSec, sizeof(bootSec), 1, file);

	bootSec.BPB_BytesPerSec = convertEndian16(bootSec.BPB_BytesPerSec);
	bootSec.BPB_RsvdSecCnt = convertEndian16(bootSec.BPB_RsvdSecCnt);
	bootSec.BPB_RootEntCnt = convertEndian16(bootSec.BPB_RootEntCnt);
	bootSec.BPB_TotSec16 = convertEndian16(bootSec.BPB_TotSec16);
	bootSec.BPB_FATSz16 = convertEndian16(bootSec.BPB_FATSz16);
	bootSec.BPB_SecPerTrk = convertEndian16(bootSec.BPB_SecPerTrk);
	bootSec.BPB_NumHeads = convertEndian16(bootSec.BPB_NumHeads);
	bootSec.BPB_HiddSec = convertEndian32(bootSec.BPB_HiddSec);
	bootSec.BPB_TotSec32 = convertEndian32(bootSec.BPB_TotSec32);
	bootSec.BPB_FATSz32 = convertEndian32(bootSec.BPB_FATSz32);
	bootSec.BPB_ExtFlags = convertEndian16(bootSec.BPB_ExtFlags);
	bootSec.BPB_FSVer = convertEndian16(bootSec.BPB_FSVer);
	bootSec.BPB_RootClus = convertEndian32(bootSec.BPB_RootClus);
	bootSec.BPB_FSInfo = convertEndian16(bootSec.BPB_FSInfo);
	bootSec.BPB_BackupBootSector
			= convertEndian16(bootSec.BPB_BackupBootSector);
}

void readFSInfo() {
	fseek(file, bootSec.BPB_FSInfo * bootSec.BPB_BytesPerSec, SEEK_SET);
	fread((void*) &FSInfo, sizeof(FSInfo), 1, file);

	FSInfo.FSI_LeadSig = convertEndian32(FSInfo.FSI_LeadSig);
	FSInfo.FSI_StructSig = convertEndian32(FSInfo.FSI_StructSig);
	FSInfo.FSI_Free_Count = convertEndian32(FSInfo.FSI_Free_Count);
	FSInfo.FSI_Nxt_Free = convertEndian32(FSInfo.FSI_Nxt_Free);
	FSInfo.FSI_TrailSig = convertEndian32(FSInfo.FSI_TrailSig);
}

void readRoot() {
	rootEnts = readCluster(bootSec.BPB_RootClus);
}

struct secOfEnts readCluster(uint clusterNum) {
	struct secOfEnts temp;

	fseek(file, clusterOffsetInBytes(clusterNum), SEEK_SET);
	fread((void*) &temp, sizeof(temp), 1, file);

	int x;
	for (x = 0; x < ENT_PER_CLUS; x++) {
		temp.entries[x]. DIR_CrtTime = convertEndian16(
				temp.entries[x].DIR_CrtTime);
		temp.entries[x].DIR_CrtDate = convertEndian16(
				temp.entries[x].DIR_CrtDate);
		temp.entries[x].DIR_LstAccDate = convertEndian16(
				temp.entries[x].DIR_LstAccDate);
		temp.entries[x].DIR_FstClustHI = convertEndian16(
				temp.entries[x].DIR_FstClustHI);
		temp.entries[x].DIR_WrtTime = convertEndian16(
				temp.entries[x].DIR_WrtTime);
		temp.entries[x].DIR_WrtDate = convertEndian16(
				temp.entries[x].DIR_WrtDate);
		temp.entries[x].DIR_FstClusLO = convertEndian16(
				temp.entries[x].DIR_FstClusLO);
		temp.entries[x].DIR_FileSize = convertEndian32(
				temp.entries[x].DIR_FileSize);
	}

	return temp;
}

void calculations() {
	rootDirSectors = ((bootSec.BPB_RootEntCnt * 32) + (bootSec.BPB_BytesPerSec
			- 1)) / bootSec.BPB_BytesPerSec;
	firstDataSector = bootSec.BPB_RsvdSecCnt + (bootSec.BPB_NumFATs
			* bootSec.BPB_FATSz32) + rootDirSectors;
}

// returns the byte offset from the beginning of the file to clusterNum
uint32_t clusterOffsetInBytes(uint clusterNum) {
	uint32_t bytes = (((clusterNum - 2) * bootSec.BPB_SecPerClus)
			+ firstDataSector) * bootSec.BPB_BytesPerSec;
	return bytes;
}

// returns the byte offset from the beginning of the file to the FAT entry for clusterNum
uint32_t thisFatEntOffset(uint clusterNum, uint fatNum) {
	uint fatOffset = clusterNum * 4;
	uint FATOffsetInBytes = bootSec.BPB_RsvdSecCnt * bootSec.BPB_BytesPerSec
			+ (fatNum - 1) * bootSec.BPB_FATSz32 * bootSec.BPB_BytesPerSec;

	return fatOffset + FATOffsetInBytes;
}

uint32_t readFatEntry(uint clusterNum) {
	uint32_t offset = thisFatEntOffset(clusterNum, 1);
	uint32_t entry;

	fseek(file, offset, SEEK_SET);
	fread((void*) &entry, sizeof(entry), 1, file);

	if(clusterNum == entry)
		return EoC;
	else
		return entry;
}

void writeFatEntry(uint clusterNum, uint32_t newValue, uint fatNum) {
	uint32_t offset = thisFatEntOffset(clusterNum, fatNum);

	newValue = convertEndian32(newValue);

	fseek(file, offset, SEEK_SET);
	fwrite((void*) &newValue, sizeof(newValue), 1, file);
}

uint32_t findFreeCluster() {
	int x;

	for (x = 0; x < bootSec.BPB_FATSz32; x++) {
		int cur = FSInfo.FSI_Nxt_Free + x % bootSec.BPB_FATSz32;
		if (readFatEntry(cur) == 0) {
			return cur;
		}
	}

	return EoC;
}

void updateSectorDirEnts(uint clusterNum, struct secOfEnts ents) {
	uint32_t offset = clusterOffsetInBytes(clusterNum);

	int x;
	for (x = 0; x < ENT_PER_CLUS; x++) {
		ents.entries[x].DIR_CrtTime = convertEndian16(
				ents.entries[x].DIR_CrtTime);
		ents.entries[x].DIR_CrtDate = convertEndian16(
				ents.entries[x].DIR_CrtDate);
		ents.entries[x].DIR_LstAccDate = convertEndian16(
				ents.entries[x].DIR_LstAccDate);
		ents.entries[x].DIR_FstClustHI = convertEndian16(
				ents.entries[x].DIR_FstClustHI);
		ents.entries[x].DIR_WrtTime = convertEndian16(
				ents.entries[x].DIR_WrtTime);
		ents.entries[x].DIR_WrtDate = convertEndian16(
				ents.entries[x].DIR_WrtDate);
		ents.entries[x].DIR_FstClusLO = convertEndian16(
				ents.entries[x].DIR_FstClusLO);
		ents.entries[x].DIR_FileSize = convertEndian32(
				ents.entries[x].DIR_FileSize);
	}

	fseek(file, offset, SEEK_SET);
	fwrite((void*) &ents, sizeof(ents), 1, file);
}

void updateFSInfo() {
	fseek(file, bootSec.BPB_FSInfo * bootSec.BPB_BytesPerSec, SEEK_SET);
	fwrite((void*) &FSInfo, sizeof(FSInfo), 1, file);
}

uint32_t findLastFileFatEntry(uint clusterNum) {
	uint32_t curClus = clusterNum;
	uint32_t prevClus;

	while (curClus < EoC) {
		prevClus = curClus;

		curClus = readFatEntry(curClus);
	}

	return prevClus;
}

bool allocateNewCluster(uint clusterNum) {
	if (FSInfo.FSI_Free_Count == 0)
		return false;

	uint32_t nextFreeCluster = FSInfo.FSI_Nxt_Free;
	uint32_t endOC = findLastFileFatEntry(clusterNum);
	writeFatEntry(endOC, nextFreeCluster, 1); // add another cluster
	writeFatEntry(nextFreeCluster, EoC, 1); // set new cluster as EoC
	// adjust backup FAT too
	writeFatEntry(endOC, nextFreeCluster, 2); // add another cluster
	writeFatEntry(nextFreeCluster, EoC, 2); // set new cluster as EoC
	nextFreeCluster = findFreeCluster();
	FSInfo.FSI_Nxt_Free = nextFreeCluster;
	FSInfo.FSI_Free_Count--;
	updateFSInfo();
	return true;
}

void addDotFiles(uint clusterNum, uint parentClusterNum) {
	uint32_t nextFreeCluster;
	struct secOfEnts ents = readCluster(clusterNum);

	ents.entries[0].DIR_Attr = 0x10;
	strcpy(ents.entries[0].DIR_Name, ".");
	ents.entries[0].DIR_FstClusLO = (uint16_t) clusterNum;
	nextFreeCluster = clusterNum >> 16;
	ents.entries[0].DIR_FstClustHI = (uint16_t) nextFreeCluster;

	ents.entries[1].DIR_Attr = 0x10;
	strcpy(ents.entries[1].DIR_Name, "..");
	ents.entries[1].DIR_FstClusLO = (uint16_t) parentClusterNum;
	nextFreeCluster = parentClusterNum >> 16;
	ents.entries[1].DIR_FstClustHI = (uint16_t) nextFreeCluster;
	updateSectorDirEnts(clusterNum, ents);
}

uint16_t convertEndian16(uint16_t num) {
	return (!is_host_little_endian()) ? htobe16(num) : htole16(num);
}

uint32_t convertEndian32(uint32_t num) {
	return (!is_host_little_endian()) ? htobe32(num) : htole32(num);
}

uint64_t convertEndian64(uint64_t num) {
	return (!is_host_little_endian()) ? htobe64(num) : htole64(num);
}

